#include "auditcryptogram.h"

#include <io.h>

#include <iostream>
#include <ios>
#include <string>
#include <iomanip>
#include <fstream>
#include <stdio.h>
#include <vector>

#include <QtCore>

#include <openssl/aes.h>
#include <openssl/md5.h>
#include "keytree/keytree.h"

#include "signature/util.h"
#include "signature/base58.h"

bool fTestNet = false;
const std::string strMessageMagic = "Bitcoin Signed Message:\n"; // from main.cpp

const std::string strStdCryptogramMagic = "AuditApi Signed Cryptolalia:\n";

AUDITAPISHARED_EXPORT int apiEncryptionCryptolalia(const char * const _data,
                                                   int _dataSize,
                             char* _cryptolalia, const char * const _key) {
    if (_cryptolalia == Q_NULLPTR
        || _data == Q_NULLPTR
        || _key == Q_NULLPTR)
        return 87;
    if (_dataSize <= 0)
        _dataSize = strlen(_data);

    // md5
    unsigned char md5[64] = {};
    AES_KEY key;
    unsigned char iv[AES_BLOCK_SIZE] = {};//加密的初始化向量
    MD5((const unsigned char*)_key, strlen(_key), md5);

    // AES 加密
    AES_set_encrypt_key(md5, strlen((const char*)md5)*8, &key);
    AES_cbc_encrypt((const unsigned char*)_data, (unsigned char*)_cryptolalia,
                    _dataSize, &key, iv, AES_ENCRYPT);
    return 0;
}

AUDITAPISHARED_EXPORT int apiDecryptionCryptolalia(const char * const _cryptolalia,
                                int _cryptolaliaSize,
                                char* _data, const char * const _key) {
    if (_cryptolalia == Q_NULLPTR
        || _data == Q_NULLPTR
        || _key == Q_NULLPTR)
        return 87;
    if (_cryptolaliaSize <= 0)
        _cryptolaliaSize = strlen(_cryptolalia);

    // md5
    unsigned char md5[64] = {};
    AES_KEY key;
    unsigned char iv[AES_BLOCK_SIZE] = {};//加密的初始化向量
    MD5((const unsigned char*)_key, strlen(_key),md5);


    // AES 解密
    AES_set_decrypt_key(md5, strlen((const char*)md5)*8, &key);

    AES_cbc_encrypt((const unsigned char*)_cryptolalia, (unsigned char*)_data,
                    _cryptolaliaSize, &key, iv, AES_DECRYPT);

    return 0;
}



AUDITAPISHARED_EXPORT int apiGeneratedChildPrvKey(const char * const _masterPrvKey,
                       const char * const _chain,
                       char * const _childPrvKey) {
    if (_masterPrvKey == Q_NULLPTR
            || _chain == Q_NULLPTR
            || _childPrvKey == Q_NULLPTR) {
        return 84;
    }

    // 生成子私钥
    std::string prv_key = srOutputChildPrvKey(_masterPrvKey,_chain, fTestNet);

    if (prv_key.empty())
        return -1;

    memcpy(_childPrvKey, prv_key.c_str(), prv_key.length());
    return 0;
}


AUDITAPISHARED_EXPORT int apiMatchPubPrvKey(const char * const _prvKey,
                                            const char * const _pubKey) {
    if(_prvKey == Q_NULLPTR || _pubKey == Q_NULLPTR) {
        return 87;
    }

    std::string prv_addr, pub_addr;

    // 推导私钥地址
    prv_addr = outputExtKeysFromExtKey(_prvKey,"0/0");

    // 推导公钥地址
    pub_addr = outputExtKeysFromExtKey(_pubKey,"0/0");


    // 匹配地址
    if (prv_addr != pub_addr)
        return -1;
    return 0;
}


/**
* @brief : 文本数据标准加密流程
* @param : _filename 文件绝对路径
* @param : _mode     扩展参数
* @return: 0成功，非0失败
* @remark: 统一使用 Json 数据格式
*          AES(文本文件,MD5("SR"))
*/
AUDITAPISHARED_EXPORT int apiStdEncryptionFile(const char * const _filename,
                                               const int _mode) {
    Q_UNUSED(_mode);

    if (_filename == Q_NULLPTR)
        return 87;

    if (0 != access(_filename,F_OK))
        return 2;


    /// [1] 修改文件名字
    std::string new_filename = _filename;
    new_filename += ".audit_bak";
    rename(_filename, new_filename.c_str());

    /// [2] 读取文件全部内容
    std::fstream in_file;
    in_file.open(new_filename, std::ios::in | std::ios::binary | std::ios::ate);
    if (!in_file.is_open()) {
        return 4;
    }
    // 获得文件总大小
    long file_size = in_file.tellg();
    // 移至文件开始处
    in_file.seekg (0, std::ios::beg);
    char* buffer = new char [file_size];
    char* out_buffer = new char [file_size + 16];
    memset(buffer,0,file_size);
    memset(out_buffer,0,file_size + 16);
    // 读取文件
    in_file.read(buffer,file_size);
    in_file.close();

    /// [3] 约定 key 写死
    char appoint_key[] = "SR";
    unsigned char iv[AES_BLOCK_SIZE] = {};//加密的初始化向量
    // md5
    unsigned char md5_key[64] = {};
    AES_KEY key;
    MD5((const unsigned char*)appoint_key, strlen(appoint_key),md5_key);


    /// [4] AES 加密
    AES_set_encrypt_key(md5_key,
                        strlen((const char*)md5_key)*8,
                        &key);
    AES_cbc_encrypt((const unsigned char*)buffer, (unsigned char*)out_buffer,
                    file_size, &key, iv, AES_ENCRYPT);

    int ret = 0;
    do {
        /// [5] 保存密文
        std::fstream out_file;
        out_file.open(_filename, std::ios::out| std::ios::binary | std::ios::trunc);
        if (!out_file.is_open()) {
            qWarning() << _filename
                    << "file open failed. (apiStdEncryptionFile.out_file.open)";
            ret = 4;
            break;
        }
        out_file.write(out_buffer, file_size);
        out_file.close();
    }while(false);


    /// [6] 释放资源
    if (buffer != Q_NULLPTR) {
        delete[] buffer;
        buffer = Q_NULLPTR;
    }
    if (out_buffer != Q_NULLPTR) {
        delete[] out_buffer;
        out_buffer = Q_NULLPTR;
    }
    // 删除备份文件
    if (ret == 0)
        remove(new_filename.c_str());
    else
        rename(new_filename.c_str(),_filename);
    return ret;
}


AUDITAPISHARED_EXPORT int apiStdDecryptionFile(const char * const _filename,
                         const int _mode) {
    Q_UNUSED(_mode);

    if (_filename == Q_NULLPTR)
        return 87;

    if (0 != access(_filename,F_OK)) {
        return 2;
    }

    /// [1] 修改文件名字
    std::string new_filename = _filename;
    new_filename += ".audit_bak";
    rename(_filename, new_filename.c_str());

    /// [2] 读取文件全部内容
    std::fstream in_file;
    in_file.open(new_filename, std::ios::in | std::ios::binary | std::ios::ate);
    if (!in_file.is_open()) {
        return 4;
    }
    // 获得文件总大小
    size_t file_size = in_file.tellg();
    // 移至文件开始处
    in_file.seekg (0, std::ios::beg);
    char* buffer = new char [file_size];
    char* out_buffer = new char [file_size + 16];
    memset(buffer,0,file_size);
    memset(out_buffer,0,file_size + 16);
    // 读取文件
    in_file.read(buffer,file_size);
    in_file.close();

    /// [3] 约定 key 写死
    char appoint_key[] = "SR";
    unsigned char iv[AES_BLOCK_SIZE] = {};//加密的初始化向量
    // md5
    unsigned char md5[64] = {};
    AES_KEY key;
    MD5((const unsigned char*)appoint_key, strlen(appoint_key),md5);

    /// [4] AES 加密
    AES_set_decrypt_key(md5, strlen((const char*)md5)*8, &key);
    AES_cbc_encrypt((const unsigned char*)buffer, (unsigned char*)out_buffer,
                    file_size, &key, iv, AES_DECRYPT);

    int ret = 0;
    do {
        /// [5] 保存密文
        std::fstream out_file;
        out_file.open(_filename, std::ios::out| std::ios::binary | std::ios::trunc);
        if (!out_file.is_open()) {
            qDebug() << _filename
                    << "file open failed. (apiStdEncryptionFile.out_file.open)";
            ret = 4;
            break;
        }
        out_file.write(out_buffer, file_size);
        out_file.close();
    }while(false);


    /// [6] 释放资源
    if (buffer != Q_NULLPTR) {
        delete[] buffer;
        buffer = Q_NULLPTR;
    }
    if (out_buffer != Q_NULLPTR) {
        delete[] out_buffer;
        out_buffer = Q_NULLPTR;
    }
    // 删除备份文件
    if (ret == 0)
        remove(new_filename.c_str());
    else
        rename(new_filename.c_str(),_filename);
    return ret;
}

AUDITAPISHARED_EXPORT int apiSignature(const char * const _prvKey,
                                       const char * const _data,
                                       char * const signa) {

    std::string strSecret = _prvKey;
    std::string strMessage = _data;

//    const std::string& strMessageMagic = "Bitcoin Signed Message:\n"; // from main.cpp
    CBitcoinSecret vchSecret;
    bool fGood = vchSecret.SetString(strSecret);

    if (!fGood) {
        qDebug("Private key is not good\n");
        return 87;
    }

    CKey key;
    bool fCompressed;
    CSecret csecret = vchSecret.GetSecret(fCompressed);
    key.SetSecret(csecret, fCompressed);

    CHashWriter ss(0);
    ss << strMessageMagic;
    ss << strMessage;

    std::vector<unsigned char> vchSig;
    if (!key.SignCompact(ss.GetHash(), vchSig)) {
        qDebug("Sign failed\n");
        return 1;
    }
    std::string out_signa = EncodeBase64(&vchSig[0], vchSig.size());
    memcpy(signa, out_signa.c_str(), out_signa.length());

    return 0;
}
AUDITAPISHARED_EXPORT int apiVerifySignature(const char * const _addr,
                                             const char * const _signa,
                                             const char * const _data) {

    std::string strAddress  = _addr;
    std::string strSign     = _signa;
    std::string strMessage  = _data;

    CBitcoinAddress addr(strAddress);
    if (!addr.IsValid()) {
        qDebug("Invalid address\n");
        return 87;
    }

    CKeyID keyID;
    if (!addr.GetKeyID(keyID)) {
        qDebug("Address does not refer to key\n");
        return 87;
    }

    bool fInvalid = false;
    std::vector<unsigned char> vchSig = DecodeBase64(strSign.c_str(), &fInvalid);

    if (fInvalid) {
        qDebug("Malformed base64 encoding\n");
        return 87;
    }

    CHashWriter ss(0);
    ss << strMessageMagic;
    ss << strMessage;

    CKey key;
    if (!key.SetCompactSignature(ss.GetHash(), vchSig)) {
        qDebug("Error reading signature\n");
        return 5;
    }

    // 0 is "success" in standard UNIX return codes
    if (key.GetPubKey().GetID() == keyID) {
#ifdef QT_DEBUG
        qDebug("pass\n");
#endif
        return 0;
    } else {
#ifdef QT_DEBUG
        qDebug("fail\n");
#endif
        return 1;
    }
    return 0;
}
AUDITAPISHARED_EXPORT int apiStdHashData(const char * const _data,
                                     unsigned char * const _cryptolalia) {
    // md5(magic + _data)
#if 0
    std::string data = strStdCryptogramMagic;
    data += _data;
#else
    std::string data = _data;
#endif
    unsigned char uc_md5[64] = {};
    MD5((const unsigned char*)data.data(), data.length(), uc_md5);

    // 16字节md5码 序列化转换 16进制字节流
    std::ostringstream oss;
    oss << std::hex << std::setfill('0');
    for (int i = 0; i < 16; ++i)
        oss << std::setw(2) << int(uc_md5[i]);

    std::string hex_md5 = oss.str();
    memcpy(_cryptolalia, hex_md5.c_str(), hex_md5.length());

    return 0;
}

AUDITAPISHARED_EXPORT int apiGeneratedMainPrvKey(const char * const _seed,
                           const char * const _chain,
                           char * const _prvKey) {

    std::string prv_key = srOutputExtKeysFromSeed(_seed,_chain, fTestNet);

    if (prv_key.empty())
        return -1;

    memcpy(_prvKey, prv_key.c_str(), prv_key.length());

    return 0;
}

AUDITAPISHARED_EXPORT int apiGeneratedPrvKeyInfo(__api_in const char* const _mainPrvKey,
                                                 __api_out char* const _ekPubKey,
                                                 __api_out char* const _pubKey,
                                                 __api_out char* const _prvKey,
                                                 __api_out char* const _address)
{
    if (_mainPrvKey == Q_NULLPTR) {
        return -1;
    }

    std::string ek_pub_key, pub_key, prv_key, addr;
    int ret = srOutputKeyInfo(_mainPrvKey, ek_pub_key, pub_key, prv_key, addr, fTestNet);



    if (_ekPubKey != Q_NULLPTR)
        memcpy(_ekPubKey, ek_pub_key.c_str(), ek_pub_key.length());
    if (_pubKey != Q_NULLPTR)
        memcpy(_pubKey, pub_key.c_str(), pub_key.length());
    if (_prvKey != Q_NULLPTR)
        memcpy(_prvKey, prv_key.c_str(), prv_key.length());
    if (_address != Q_NULLPTR)
        memcpy(_address, addr.c_str(), addr.length());

    return ret;
}

int apiSetBitcoinTest(bool _test)
{
    fTestNet = _test;
}
